/*-------------------------------------------------------------------------
 *
 * slot.c
 *	   复制插槽管理。
 *
 *
 * Copyright (c) 2012-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/replication/slot.c
 *
 * NOTES
 *
 * 复制插槽用于保持有关从此集群发出的复制流的状态。
 * 它们的主要目的是防止 WAL 或旧元组版本的过早删除，
 * 以免干扰复制；它们还对监控目的很有帮助。
 * 插槽需要是永久的（以允许重启）、防崩溃并可在备用中分配
 * （以支持级联设置）。插槽在备用上可用的要求排除了将它们存储
 * 在系统目录中。
 *
 * 每个复制插槽在 $PGDATA/pg_replslot 目录中都有自己的目录。
 * 在该目录内，状态文件将包含插槽自己的数据。如果需要，可以
 * 在该文件旁边存储额外的数据。在服务器运行时，为了提高效率，
 * 状态数据也会缓存到内存中。
 *
 * 必须以独占模式获取 ReplicationSlotAllocationLock 以分配或释放插槽。
 * 必须以共享模式获取 ReplicationSlotControlLock 以迭代插槽，并在
 * 独占模式下更改插槽的 in_use 标志。每个插槽中的其余数据受其互斥锁保护。
 *
 *-------------------------------------------------------------------------
 */
#ifdef FDD
/*
*-------------------------------------------------------------------------
 * 非故障转移插槽不受WAL日志记录的限制，并且可以
 * 在备机上使用（尽管目前仅支持物理插槽）。它们使用临时文件写入和交换以保证崩溃安全。
 *
 * 在主节点上创建的故障转移插槽会生成WAL记录，这些记录
 * 保持备机上的插槽副本。如果备机被提升，则故障转移插槽
 * 允许访问的重启，就好像在访问原始主节点一样，从而允许
 * 时间线的变更。副本在删除WAL时考虑插槽位置，以确保
 * 在提升后能够满足插槽的需求。对于逻辑解码插槽，插槽的内部
 * 状态保持最新，以便在提升后可以使用。

*-------------------------------------------------------------------------
*/
#endif

#include "postgres.h"

#include <unistd.h>
#include <sys/stat.h>

#include "access/transam.h"
#include "access/xlog_internal.h"
#include "common/string.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "replication/slot.h"
#ifdef FDD
#include "replication/slot_xlog.h"
#include "access/xloginsert.h"
#endif
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "utils/builtins.h"

/*
 * 磁盘上的复制插槽数据结构。
 */
typedef struct ReplicationSlotOnDisk
{
	/* 该结构的第一部分需要与版本无关 */

	/* 不受校验和覆盖的数据 */
	uint32		magic;
	pg_crc32c	checksum;

	/* 受校验和覆盖的数据 */
	uint32		version;
	uint32		length;

	/*
	 * 插槽中的实际数据可能会根据上述
	 * '版本'而有所不同。
	 */

	ReplicationSlotPersistentData slotdata;
} ReplicationSlotOnDisk;

/* 与版本无关的数据大小 */
#define ReplicationSlotOnDiskConstantSize \
	offsetof(ReplicationSlotOnDisk, slotdata)
/* 插槽中不受校验和覆盖的部分大小 */
#define ReplicationSlotOnDiskNotChecksummedSize  \
	offsetof(ReplicationSlotOnDisk, version)
/* 受校验和覆盖的部分大小 */
#define ReplicationSlotOnDiskChecksummedSize \
	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskNotChecksummedSize
/* 与版本相关的插槽数据大小 */
#define ReplicationSlotOnDiskV2Size \
	sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize

#define SLOT_MAGIC		0x1051CA1	/* 格式标识符 */
#define SLOT_VERSION	2		/* 新文件的版本 */

/* 复制插槽管理的控制数组 */
ReplicationSlotCtlData *ReplicationSlotCtl = NULL;

/* 我后端的复制插槽在共享内存数组中 */
ReplicationSlot *MyReplicationSlot = NULL;

/* GUCs */
int			max_replication_slots = 0;	/* 最大的复制插槽数量 */

static void fc_ReplicationSlotShmemExit(int fc_code, Datum fc_arg);
static void fc_ReplicationSlotDropAcquired(void);
static void fc_ReplicationSlotDropPtr(ReplicationSlot *fc_slot);

/* 内部持久性函数 */
#ifndef FDD
static void fc_RestoreSlotFromDisk(const char *fc_name);
#else
static void fc_RestoreSlotFromDisk(const char *fc_name, bool fc_drop_nonfailover_slots);
#endif
static void fc_CreateSlotOnDisk(ReplicationSlot *fc_slot);
static void fc_SaveSlotToPath(ReplicationSlot *fc_slot, const char *fc_path, int fc_elevel);

#ifdef FDD
/* 内部重做函数 */
static void fc_ReplicationSlotRedoCreateOrUpdate(ReplicationSlotPersistentData * fc_xlrec);
static void fc_ReplicationSlotRedoDrop(const char * fc_slotname);
#endif

/*
 * 报告ReplicationSlotsShmemInit所需的共享内存空间。
 */
Size ReplicationSlotsShmemSize(void)
{
	Size		fc_size = 0;

	if (max_replication_slots == 0)
		return fc_size;

	fc_size = offsetof(ReplicationSlotCtlData, replication_slots);
	fc_size = add_size(fc_size,
					mul_size(max_replication_slots, sizeof(ReplicationSlot)));

	return fc_size;
}

/*
 * 分配并初始化用于复制插槽的共享内存。
 */
void ReplicationSlotsShmemInit(void)
{
	bool		fc_found;

	if (max_replication_slots == 0)
		return;

	ReplicationSlotCtl = (ReplicationSlotCtlData *)
		ShmemInitStruct("ReplicationSlot Ctl", ReplicationSlotsShmemSize(),
						&fc_found);

	if (!fc_found)
	{
		int			fc_i;

		/* 第一次通过，所以初始化 */
		MemSet(ReplicationSlotCtl, 0, ReplicationSlotsShmemSize());

		for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
		{
			ReplicationSlot *fc_slot = &ReplicationSlotCtl->replication_slots[fc_i];

			/* 其余部分由上面的memset清零 */
			SpinLockInit(&fc_slot->mutex);
			LWLockInitialize(&fc_slot->io_in_progress_lock,
							 LWTRANCHE_REPLICATION_SLOT_IO);
			ConditionVariableInit(&fc_slot->active_cv);
		}
	}
}

/*
 * 注册复制插槽清理和释放的回调。
 */
void ReplicationSlotInitialize(void)
{
	before_shmem_exit(fc_ReplicationSlotShmemExit, 0);
}

/*
 * 释放和清理复制插槽。
 */
static void fc_ReplicationSlotShmemExit(int fc_code, Datum fc_arg)
{
	/* 确保释放活动的复制插槽 */
	if (MyReplicationSlot != NULL)
		ReplicationSlotRelease();

	/* 还要清理所有临时插槽。 */
	ReplicationSlotCleanup();
}

/*
 * 检查传递的插槽名称是否有效，并在elevel上报告错误。
 *
 * 插槽名称可以由[a-z0-9_]{1,NAMEDATALEN-1}组成，这应该允许
 * 在每个受支持的操作系统上使用该名称作为目录名称。
 *
 * 如果elevel < ERROR，返回目录名称是否有效。
 */
bool ReplicationSlotValidateName(const char *fc_name, int fc_elevel)
{
	const char *fc_cp;

	if (strlen(fc_name) == 0)
	{
		ereport(fc_elevel,
				(errcode(ERRCODE_INVALID_NAME),
				 errmsg("replication slot name \"%s\" is too short",
						fc_name)));
		return false;
	}

	if (strlen(fc_name) >= NAMEDATALEN)
	{
		ereport(fc_elevel,
				(errcode(ERRCODE_NAME_TOO_LONG),
				 errmsg("replication slot name \"%s\" is too long",
						fc_name)));
		return false;
	}

	for (fc_cp = fc_name; *fc_cp; fc_cp++)
	{
		if (!((*fc_cp >= 'a' && *fc_cp <= 'z')
			  || (*fc_cp >= '0' && *fc_cp <= '9')
			  || (*fc_cp == '_')))
		{
			ereport(fc_elevel,
					(errcode(ERRCODE_INVALID_NAME),
					 errmsg("replication slot name \"%s\" contains invalid character",
							fc_name),
					 errhint("Replication slot names may only contain lower case letters, numbers, and the underscore character.")));
			return false;
		}
	}
	return true;
}

/*
 * 创建一个新的复制插槽，并将其标记为此后端使用。
 *
 * name: 插槽的名称
 * db_specific: 逻辑解码是数据库特定的；如果插槽将用于此，则传入true， 否则传入false。
 * two_phase: 允许准备事务的解码。我们仅在插槽创建时允许启用此选项。如果我们允许在解码期间更改此选项，则很可能第一次因为未启用此选项而跳过准备。在获取更改时，如果启用了two_phase选项，则可能跳过准备，因为到那时开始解码点已经移动。因此用户只会获得提交的准备。
 */
void ReplicationSlotCreate(const char *fc_name, bool fc_db_specific,
#ifndef FDD
					  ReplicationSlotPersistency fc_persistency, bool fc_two_phase)
#else
					  ReplicationSlotPersistency fc_persistency, bool fc_two_phase, bool fc_failover)
#endif
{
	ReplicationSlot *fc_slot = NULL;
	int			fc_i;

	Assert(MyReplicationSlot == NULL);

	ReplicationSlotValidateName(fc_name, ERROR);

#ifdef FDD
	if (fc_failover && RecoveryInProgress())
			ereport(ERROR,
					(errmsg("a failover slot may not be created on a replica"),
					 errhint("Create the slot on the master server instead")));
#endif

	/*
	 * 如果其他后端与我们并发运行此代码，我们可能都会分配相同的插槽，这将很糟糕。我们也有可能错过名称冲突。此外，我们不希望在某人忙于清理旧插槽时尝试创建新插槽，因为我们可能都会在同一目录中进行操作。
	 */
	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);

	/*
	 * 检查名称冲突，并识别一个可分配的插槽。我们需要以共享模式保持ReplicationSlotControlLock，以便在查看期间没有其他人可以更改in_use标志。
	 */
	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s = &ReplicationSlotCtl->replication_slots[fc_i];

		if (fc_s->in_use && strcmp(fc_name, NameStr(fc_s->data.name)) == 0)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("replication slot \"%s\" already exists", fc_name)));
		if (!fc_s->in_use && fc_slot == NULL)
			fc_slot = fc_s;
	}
	LWLockRelease(ReplicationSlotControlLock);

	/* 如果所有插槽都在使用中，我们就没运气了。 */
	if (fc_slot == NULL)
#ifdef FDD //cppcheck
	{
#endif
		ereport(ERROR,
				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
				 errmsg("all replication slots are in use"),
				 errhint("Free one or increase max_replication_slots.")));
#ifdef FDD //cppcheck
		return;
	}
#endif
	/*
	 * 由于此插槽未在使用中，除了in_use字段外，没有人应该查看它的任何部分，除非他们试图分配它。并且由于我们持有ReplicationSlotAllocationLock，除了我们之外没有人可以这样做。因此初始化插槽是安全的。
	 */
	Assert(!fc_slot->in_use);
	Assert(fc_slot->active_pid == 0);

	/* 首先初始化持久数据 */
	memset(&fc_slot->data, 0, sizeof(ReplicationSlotPersistentData));
	namestrcpy(&fc_slot->data.name, fc_name);
	fc_slot->data.database = fc_db_specific ? MyDatabaseId : InvalidOid;
	fc_slot->data.persistency = fc_persistency;
	fc_slot->data.two_phase = fc_two_phase;
	fc_slot->data.two_phase_at = InvalidXLogRecPtr;
#ifdef FDD
	fc_slot->data.failover = fc_failover;
#endif

	/* 然后初始化仅存在于共享内存中的数据 */
	fc_slot->just_dirtied = false;
	fc_slot->dirty = false;
	fc_slot->effective_xmin = InvalidTransactionId;
	fc_slot->effective_catalog_xmin = InvalidTransactionId;
	fc_slot->candidate_catalog_xmin = InvalidTransactionId;
	fc_slot->candidate_xmin_lsn = InvalidXLogRecPtr;
	fc_slot->candidate_restart_valid = InvalidXLogRecPtr;
	fc_slot->candidate_restart_lsn = InvalidXLogRecPtr;

	/*
	 * 在磁盘上创建插槽。我们尚未真正标记插槽已分配，因此如果出错，则不需要进行特殊清理。
	 */
	fc_CreateSlotOnDisk(fc_slot);

	/*
	 * 我们需要暂时阻止任何其他后端在我们翻转in_use标志时遍历插槽。我们还需要在持有ControlLock时设置active标志，否则并发的ReplicationSlotAcquire()也可能获得该插槽。
	 */
	LWLockAcquire(ReplicationSlotControlLock, LW_EXCLUSIVE);

	fc_slot->in_use = true;

	/* 我们现在可以将插槽标记为活动，这使其成为我们的插槽。 */
	SpinLockAcquire(&fc_slot->mutex);
	Assert(fc_slot->active_pid == 0);
	fc_slot->active_pid = MyProcPid;
	SpinLockRelease(&fc_slot->mutex);
	MyReplicationSlot = fc_slot;

	LWLockRelease(ReplicationSlotControlLock);

	/*
	 * 为新的逻辑插槽创建统计信息条目。我们不为物理插槽收集任何统计信息，因此没有必要为其创建条目。请参见ReplicationSlotDropPtr了解为何必须在释放ReplicationSlotAllocationLock之前执行此操作。
	 */
	if (SlotIsLogical(fc_slot))
		pgstat_create_replslot(fc_slot);

	/*
	 * 现在插槽已标记为in_use和active，可以安全地让其他人尝试分配插槽。
	 */
	LWLockRelease(ReplicationSlotAllocationLock);

	/* 让每个人知道我们已修改此插槽 */
	ConditionVariableBroadcast(&fc_slot->active_cv);
}

/*
 * 搜索指定名称的复制插槽。
 *
 * 如果找到，则返回复制插槽，否则返回NULL。
 */
ReplicationSlot *
SearchNamedReplicationSlot(const char *fc_name, bool fc_need_lock)
{
	int			fc_i;
	ReplicationSlot *fc_slot = NULL;

	if (fc_need_lock)
		LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);

	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s = &ReplicationSlotCtl->replication_slots[fc_i];

		if (fc_s->in_use && strcmp(fc_name, NameStr(fc_s->data.name)) == 0)
		{
			fc_slot = fc_s;
			break;
		}
	}

	if (fc_need_lock)
		LWLockRelease(ReplicationSlotControlLock);

	return fc_slot;
}

/*
 * 返回在 ReplicationSlotCtl->replication_slots 中的复制插槽的索引。
 *
 * 这主要用于为存储复制插槽统计信息提供一个高效的键。
 */
int ReplicationSlotIndex(ReplicationSlot *fc_slot)
{
	Assert(fc_slot >= ReplicationSlotCtl->replication_slots &&
		   fc_slot < ReplicationSlotCtl->replication_slots + max_replication_slots);

	return fc_slot - ReplicationSlotCtl->replication_slots;
}

/*
 * 如果“index”处的插槽未使用，则返回 false。否则，将“name”设置为
 * 插槽的名称并返回 true。
 *
 * 这可能仅在关机期间对 pgstat_replslot.c 有用，在其他情况下会有明显的 TOCTOU 问题。
 */
bool ReplicationSlotName(int fc_index, Name fc_name)
{
	ReplicationSlot *fc_slot;
	bool		fc_found;

	fc_slot = &ReplicationSlotCtl->replication_slots[fc_index];

	/*
	 * 确保在复制名称时插槽无法被删除。无需使用自旋锁，因为
	 * 已存在的插槽的名称无法更改。
	 */
	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
	fc_found = fc_slot->in_use;
	if (fc_slot->in_use)
		namestrcpy(fc_name, NameStr(fc_slot->data.name));
	LWLockRelease(ReplicationSlotControlLock);

	return fc_found;
}

/*
 * 查找先前创建的插槽并标记为此进程使用。
 *
 * 如果 nowait 为 true 而插槽当前正被使用，则会引发错误。如果
 * nowait 为 false，则我们会睡眠，直到插槽被拥有进程释放。
 */
 #ifdef FDD
 /*
  * 设置 active_pid，并仅在成功获取时分配 MyReplicationSlot。
  *
  * 在恢复过程中尝试获取故障转移插槽时会出现错误。
 */
 #endif
void ReplicationSlotAcquire(const char *fc_name, bool fc_nowait)
{
	ReplicationSlot *fc_s;
	int			fc_active_pid;

	AssertArg(fc_name != NULL);

retry:
	Assert(MyReplicationSlot == NULL);

	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);

	/*
	 * 如果未给定要获取的插槽，则搜索具有指定名称的插槽。如果未找到
	 * 插槽，则返回 -1 或错误。
	 */
	fc_s = SearchNamedReplicationSlot(fc_name, false);
	if (fc_s == NULL || !fc_s->in_use)
	{
		LWLockRelease(ReplicationSlotControlLock);

		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("replication slot \"%s\" does not exist",
						fc_name)));
	}

	/*
	 * 这是我们想要的插槽；检查它是否在其他进程中处于活动状态。
	 * 在单用户模式下，我们不需要此检查。
	 */
	if (IsUnderPostmaster)
	{
		/*
		 * 准备在插槽上睡眠，以防其处于活动状态。（我们可能最终不会
		 * 睡眠，但我们不希望在持有自旋锁时执行此操作。）
		 */
		if (!fc_nowait)
			ConditionVariablePrepareToSleep(&fc_s->active_cv);

		SpinLockAcquire(&fc_s->mutex);
#ifdef FDD
		/*
		 * 我们只能在插槽未被其他人声称且它不是待机上的故障转移插槽时
		 * 声明插槽供我们使用。
		 */
		if (fc_s->active_pid == 0 && !(RecoveryInProgress() && fc_s->data.failover))
#else
		if (fc_s->active_pid == 0)
#endif
			fc_s->active_pid = MyProcPid;
		fc_active_pid = fc_s->active_pid;
		SpinLockRelease(&fc_s->mutex);
	}
	else
		fc_active_pid = MyProcPid;
	LWLockRelease(ReplicationSlotControlLock);

#ifdef FDD
	/*
	 * 尝试从待机中使用故障转移插槽必须失败，因为我们无法从待机写入
	 * WAL，并且无论如何没有合理的方法来从副本和主服务器推进插槽位置。
	 */
	if (RecoveryInProgress() && fc_s->data.failover)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_IN_USE),
				 errmsg("replication slot \"%s\" is reserved for use after failover",
					  fc_name)));
#endif

	/*
	 * 如果我们找到了插槽但它已在其他进程中处于活动状态，则我们
	 * 等待拥有进程通知我们已释放，或者报错。
	 */
	if (fc_active_pid != MyProcPid)
	{
		if (!fc_nowait)
		{
			/* 在这里等待直到我们收到信号，然后重新启动 */
			ConditionVariableSleep(&fc_s->active_cv,
								   WAIT_EVENT_REPLICATION_SLOT_DROP);
			ConditionVariableCancelSleep();
			goto retry;
		}

		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_IN_USE),
				 errmsg("replication slot \"%s\" is active for PID %d",
						NameStr(fc_s->data.name), fc_active_pid)));
	}
	else if (!fc_nowait)
		ConditionVariableCancelSleep(); /* 反正不需要睡眠 */

	/* 让每个人知道我们已修改此插槽 */
	ConditionVariableBroadcast(&fc_s->active_cv);

	/* 我们使此插槽处于活动状态，所以现在是我们的。 */
	MyReplicationSlot = fc_s;

	/*
	 * 调用 pgstat_acquire_replslot() 防止在 pgstat_report_replslot()
	 * 期间出现来自不同插槽的统计信息，这些统计信息是在重新启动之前等情况下存在的。
	 */
	if (SlotIsLogical(fc_s))
		pgstat_acquire_replslot(fc_s);
}

/*
 * 释放此后端认为拥有的复制插槽。
 *
 * 此后端或其他后端可以稍后重新获取该插槽。
 * 此插槽所需的资源将被保留。
 */
void ReplicationSlotRelease(void)
{
	ReplicationSlot *fc_slot = MyReplicationSlot;

	Assert(fc_slot != NULL && fc_slot->active_pid != 0);

	if (fc_slot->data.persistency == RS_EPHEMERAL)
	{
		/*
		 * 删除插槽。在任何非 !PANIC 的情况下，这都不允许失败，
		 * 所有可能发生的情况只是对磁盘
		 * 数据的不完整清理。
		 */
		fc_ReplicationSlotDropAcquired();
	}

	/*
	 * 如果插槽需要暂时约束数据和目录 xmin 以创建目录快照，请
	 * 移除该临时约束。
	 * 快照只能在初始快照仍然被获取的情况下进行导出。
	 */
	if (!TransactionIdIsValid(fc_slot->data.xmin) &&
		TransactionIdIsValid(fc_slot->effective_xmin))
	{
		SpinLockAcquire(&fc_slot->mutex);
		fc_slot->effective_xmin = InvalidTransactionId;
		SpinLockRelease(&fc_slot->mutex);
		ReplicationSlotsComputeRequiredXmin(false);
	}

	if (fc_slot->data.persistency == RS_PERSISTENT)
	{
		/*
		 * 将持久插槽标记为不活动。我们并不是在释放它，只是
		 * 断开连接，但唤醒可能在等待它的其他人。
		 */
		SpinLockAcquire(&fc_slot->mutex);
		fc_slot->active_pid = 0;
		SpinLockRelease(&fc_slot->mutex);
		ConditionVariableBroadcast(&fc_slot->active_cv);
	}

	MyReplicationSlot = NULL;

	/* 可能在我们是简单插槽时没有设置 */
	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
	MyProc->statusFlags &= ~PROC_IN_LOGICAL_DECODING;
	ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
	LWLockRelease(ProcArrayLock);
}

/*
 * 清理当前会话中创建的所有临时插槽。
 */
void ReplicationSlotCleanup(void)
{
	int			fc_i;

	Assert(MyReplicationSlot == NULL);

restart:
	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s = &ReplicationSlotCtl->replication_slots[fc_i];

		if (!fc_s->in_use)
			continue;

		SpinLockAcquire(&fc_s->mutex);
		if (fc_s->active_pid == MyProcPid)
		{
			Assert(fc_s->data.persistency == RS_TEMPORARY);
			SpinLockRelease(&fc_s->mutex);
			LWLockRelease(ReplicationSlotControlLock);	/* 避免死锁 */

			fc_ReplicationSlotDropPtr(fc_s);

			ConditionVariableBroadcast(&fc_s->active_cv);
			goto restart;
		}
		else
			SpinLockRelease(&fc_s->mutex);
	}

	LWLockRelease(ReplicationSlotControlLock);
}

/*
 * 永久删除由传入名称标识的复制插槽。
 */
void ReplicationSlotDrop(const char *fc_name, bool fc_nowait)
{
	Assert(MyReplicationSlot == NULL);

	ReplicationSlotAcquire(fc_name, fc_nowait);

	fc_ReplicationSlotDropAcquired();
}

#ifdef FDD
static void fc_ReplicationSlotXLOGDelete(ReplicationSlot *fc_slot)
{
	xl_replslot_drop fc_xlrec;

	memcpy(&(fc_xlrec.name), NameStr(fc_slot->data.name), NAMEDATALEN);

	XLogBeginInsert();
	XLogRegisterData((char *) (&fc_xlrec), sizeof(fc_xlrec));
	(void) XLogInsert(RM_REPLSLOT_ID, XLOG_REPLSLOT_DROP);
}

/*
 * 设置/清除当前插槽的故障转移状态。
 *
 * 如果设置故障转移，强制刷新插槽，以便它能及时复制。
 *
 * 如果清除故障转移，写入插槽删除消息到WAL，以便
 * 插槽从副本中删除。
 *
 * 我们不确保副本实际上保留了故障转移插槽所需的所有WAL。
 * 这由用户负责。
 */
void ReplicationSlotAcquiredSetFailover(bool fc_failover)
{
	ReplicationSlot *fc_slot = MyReplicationSlot;

	Assert(MyReplicationSlot != NULL);

	if (fc_failover == fc_slot->data.failover)
		return;

	fc_slot->data.failover = fc_failover;

	if (fc_slot->data.failover)
	{
		ReplicationSlotMarkDirty();
		ReplicationSlotSave();
	}
	else
	{
		fc_ReplicationSlotXLOGDelete(fc_slot);
	}
}
#endif

/*
 * 永久删除当前获取的复制插槽。
 */
static void fc_ReplicationSlotDropAcquired(void)
{
	ReplicationSlot *fc_slot = MyReplicationSlot;

	Assert(MyReplicationSlot != NULL);

	/* 插槽不再被获取 */
	MyReplicationSlot = NULL;

	fc_ReplicationSlotDropPtr(fc_slot);
}

/*
 * 永久删除将在此函数返回时被释放的复制插槽。
 */
 #ifdef FDD
 /*
 * 调用者不得以共享模式持有ReplicationSlotControlLock。独占
 * 模式可以，或者完全不持有。
 */
 #endif
static void fc_ReplicationSlotDropPtr(ReplicationSlot *fc_slot)
{
	char		fc_path[MAXPGPATH];
	char		fc_tmppath[MAXPGPATH];
#ifdef FDD
	bool fc_slot_is_failover = fc_slot->data.failover;
#endif

	/*
	 * 如果其他后端与我们并行运行此代码，我们可能会尝试
	 * 在某个人试图创建同名插槽时删除一个具有特定名称的插槽。
	 */
	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
#ifdef FDD
	/* 如果我们没有重播WAL，记录XLOG中的删除 */
	if (XLogInsertAllowed() && fc_slot_is_failover)
	{
	   fc_ReplicationSlotXLOGDelete(fc_slot);
	}
#endif

	/* 生成路径名。 */
	sprintf(fc_path, "pg_replslot/%s", NameStr(fc_slot->data.name));
	sprintf(fc_tmppath, "pg_replslot/%s.tmp", NameStr(fc_slot->data.name));

	/*
	 * 在磁盘上重命名插槽目录，以便我们将不再将其识别
	 * 为有效插槽。请注意，如果这失败了，我们必须在退出之前
	 * 将插槽标记为不活动。如果我们正在删除一个临时或
	 * 瞬态插槽，我们最好不要出现严重失败，因为调用者不会期望
	 * 插槽存活，并且这可能在错误处理期间被调用。
	 */
	if (rename(fc_path, fc_tmppath) == 0)
	{
		/*
		 * 我们需要fsync()我们刚刚重命名的目录及其父目录，以
		 * 确保我们的更改以崩溃安全的方式写入磁盘。如果
		 * fsync()失败，我们不能确定更改是否写入磁盘。
		 * 目前，我们通过恐慌处理这个问题；
		 * StartupReplicationSlots()将在重启后尝试解决它。
		 */
		START_CRIT_SECTION();
		fsync_fname(fc_tmppath, true);
		fsync_fname("pg_replslot", true);
		END_CRIT_SECTION();
	}
	else
	{
		bool		fc_fail_softly = fc_slot->data.persistency != RS_PERSISTENT;
#ifdef FDD
		if (RecoveryInProgress())
		{
			fc_fail_softly = true;
		}
#endif

		SpinLockAcquire(&fc_slot->mutex);
		fc_slot->active_pid = 0;
		SpinLockRelease(&fc_slot->mutex);

		/* 唤醒等待此插槽的任何人 */
		ConditionVariableBroadcast(&fc_slot->active_cv);

		ereport(fc_fail_softly ? WARNING : ERROR,
				(errcode_for_file_access(),
				 errmsg("could not rename file \"%s\" to \"%s\": %m",
						fc_path, fc_tmppath)));
	}

	/*
	 * 插槽肯定已经消失。为了杀死它，锁定数组的并发扫描足够长的时间。
	 * 在这里清除活动PID是可以的，因为没有其他人在这里扫描数组，
	 * 也没有人可以附加到此插槽，因此无法在
	 * 不扫描数组的情况下访问它。
	 *
	 * 同时唤醒等待它的进程。
	 */
	LWLockAcquire(ReplicationSlotControlLock, LW_EXCLUSIVE);
	fc_slot->active_pid = 0;
	fc_slot->in_use = false;
	LWLockRelease(ReplicationSlotControlLock);
	ConditionVariableBroadcast(&fc_slot->active_cv);

	/*
	 * 插槽已死，并不再阻止资源的移除，重新计算
	 * 限制。
	 */
	ReplicationSlotsComputeRequiredXmin(false);
	ReplicationSlotsComputeRequiredLSN();

	/*
	 * 如果删除目录失败，最糟糕的情况是
	 * 用户在下次服务器重启之前无法使用相同名称创建新插槽。
	 * 我们对此发出警告，但仅此而已。
	 */
	if (!rmtree(fc_tmppath, true))
		ereport(WARNING,
				(errmsg("could not remove directory \"%s\"", fc_tmppath)));

	/*
	 * 删除复制插槽的统计条目。在持有 ReplicationSlotAllocationLock 时执行此操作，以便我们不会删除在另一个会话中刚刚创建的同名插槽的统计条目。
	 */
	if (SlotIsLogical(fc_slot))
		pgstat_drop_replslot(fc_slot);

	/*
	 * 我们在最后释放这个，以便在我们仍在清理旧插槽的残留物时，没有人会开始尝试创建一个插槽。
	 */
	LWLockRelease(ReplicationSlotAllocationLock);
}

/*
 * 将当前获取的插槽的状态从内存序列化到磁盘，从而保证当前状态能在崩溃后生存。
 */
void ReplicationSlotSave(void)
{
	char		fc_path[MAXPGPATH];

	Assert(MyReplicationSlot != NULL);

	sprintf(fc_path, "pg_replslot/%s", NameStr(MyReplicationSlot->data.name));
	fc_SaveSlotToPath(MyReplicationSlot, fc_path, ERROR);
}

/*
 * 发出信号表明，如果当前获取的插槽被刷新到磁盘，将是有用的。
 *
 * 请注意，实际刷新到磁盘可能会延迟很长时间，如果出于正确性需要，明确执行一个 ReplicationSlotSave()。
 */
void ReplicationSlotMarkDirty(void)
{
	ReplicationSlot *fc_slot = MyReplicationSlot;

	Assert(MyReplicationSlot != NULL);

	SpinLockAcquire(&fc_slot->mutex);
	MyReplicationSlot->just_dirtied = true;
	MyReplicationSlot->dirty = true;
	SpinLockRelease(&fc_slot->mutex);
}

/*
 * 将标记为 RS_EPHEMERAL 的插槽转换为 RS_PERSISTENT 插槽，确保它在最终崩溃后仍然存在。
 */
 #ifdef FDD
/*
 * 故障转移插槽将在此时发出创建 xlog 记录，此前没有写入 xlog。
 */
 #endif
void ReplicationSlotPersist(void)
{
	ReplicationSlot *fc_slot = MyReplicationSlot;

	Assert(fc_slot != NULL);
	Assert(fc_slot->data.persistency != RS_PERSISTENT);

	SpinLockAcquire(&fc_slot->mutex);
	fc_slot->data.persistency = RS_PERSISTENT;
	SpinLockRelease(&fc_slot->mutex);

	ReplicationSlotMarkDirty();
	ReplicationSlotSave();
}

/*
 * 计算所有插槽中最旧的 xmin，并将其存储在 ProcArray 中。
 *
 * 如果 already_locked 为 true，则已经独占获取了 ProcArrayLock。
 */
void ReplicationSlotsComputeRequiredXmin(bool fc_already_locked)
{
	int			fc_i;
	TransactionId fc_agg_xmin = InvalidTransactionId;
	TransactionId fc_agg_catalog_xmin = InvalidTransactionId;

	Assert(ReplicationSlotCtl != NULL);

	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);

	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s = &ReplicationSlotCtl->replication_slots[fc_i];
		TransactionId fc_effective_xmin;
		TransactionId fc_effective_catalog_xmin;
		bool		fc_invalidated;

		if (!fc_s->in_use)
			continue;

		SpinLockAcquire(&fc_s->mutex);
		fc_effective_xmin = fc_s->effective_xmin;
		fc_effective_catalog_xmin = fc_s->effective_catalog_xmin;
		fc_invalidated = (!XLogRecPtrIsInvalid(fc_s->data.invalidated_at) &&
					   XLogRecPtrIsInvalid(fc_s->data.restart_lsn));
		SpinLockRelease(&fc_s->mutex);

		/* 无效的插槽不需要申请 */
		if (fc_invalidated)
			continue;

		/* 检查数据 xmin */
		if (TransactionIdIsValid(fc_effective_xmin) &&
			(!TransactionIdIsValid(fc_agg_xmin) ||
			 TransactionIdPrecedes(fc_effective_xmin, fc_agg_xmin)))
			fc_agg_xmin = fc_effective_xmin;

		/* 检查目录 xmin */
		if (TransactionIdIsValid(fc_effective_catalog_xmin) &&
			(!TransactionIdIsValid(fc_agg_catalog_xmin) ||
			 TransactionIdPrecedes(fc_effective_catalog_xmin, fc_agg_catalog_xmin)))
			fc_agg_catalog_xmin = fc_effective_catalog_xmin;
	}

	LWLockRelease(ReplicationSlotControlLock);

	ProcArraySetReplicationSlotXmin(fc_agg_xmin, fc_agg_catalog_xmin, fc_already_locked);
}

/*
 * 计算所有插槽中最旧的重启 LSN，并通知 xlog 模块。
 *
 * 注意：虽然 max_slot_wal_keep_size 理论上与此目的相关，但我们不尝试考虑这一点，因为该模块不知道该与什么进行比较。
 */
 #ifdef FDD
 /*
 * 更新 xlog 模块在所有插槽中最小重启 lsn 的副本
 */
void ReplicationSlotsComputeRequiredLSN(void)
{
	XLogSetReplicationSlotMinimumLSN(GetReplicationSlotsComputeRequiredLSN(false));
}
 /*
  * 计算所有插槽（或可选地仅计算故障转移插槽）中最旧的重启 LSN，并返回它。
  */
XLogRecPtr GetReplicationSlotsComputeRequiredLSN(bool fc_failover_only)
#else
void ReplicationSlotsComputeRequiredLSN(void)
#endif
{
	int			fc_i;
	XLogRecPtr	fc_min_required = InvalidXLogRecPtr;

	Assert(ReplicationSlotCtl != NULL);

	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s = &ReplicationSlotCtl->replication_slots[fc_i];
		XLogRecPtr	fc_restart_lsn;
#ifdef FDD
		bool		fc_failover = false;
#endif

		if (!fc_s->in_use)
			continue;

		SpinLockAcquire(&fc_s->mutex);
		fc_restart_lsn = fc_s->data.restart_lsn;
#ifdef FDD
		fc_failover = fc_s->data.failover;
#endif
		SpinLockRelease(&fc_s->mutex);

#ifdef FDD
		if (fc_failover_only && !fc_failover)
		{
			continue;
		}
#endif

		if (fc_restart_lsn != InvalidXLogRecPtr &&
			(fc_min_required == InvalidXLogRecPtr ||
			 fc_restart_lsn < fc_min_required))
			fc_min_required = fc_restart_lsn;
	}
	LWLockRelease(ReplicationSlotControlLock);

#ifdef FDD
	return fc_min_required;
#else
	XLogSetReplicationSlotMinimumLSN(fc_min_required);
#endif
}

/*
 * 计算逻辑解码插槽所需的最旧 WAL LSN。
 *
 * 如果逻辑解码被禁用或不存在逻辑插槽，则返回 InvalidXLogRecPtr。
 *
 * 注意：这返回的值 >= ReplicationSlotsComputeRequiredLSN()，因为它忽略了物理复制插槽。
 *
 * 这些结果不需要频繁，因此我们不会像 ComputeRequiredLSN() 和 ComputeRequiredXmin() 一样维护一个预计算值。
 */
XLogRecPtr ReplicationSlotsComputeLogicalRestartLSN(void)
{
	XLogRecPtr	fc_result = InvalidXLogRecPtr;
	int			fc_i;

	if (max_replication_slots <= 0)
		return InvalidXLogRecPtr;

	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);

	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s;
		XLogRecPtr	fc_restart_lsn;

		fc_s = &ReplicationSlotCtl->replication_slots[fc_i];

		/* 在持有 ReplicationSlotCtlLock 时无法更改 */
		if (!fc_s->in_use)
			continue;

		/* 我们只对逻辑插槽感兴趣 */
		if (!SlotIsLogical(fc_s))
			continue;

		/* 只读取一次，检查时增加也没关系 */
		SpinLockAcquire(&fc_s->mutex);
		fc_restart_lsn = fc_s->data.restart_lsn;
		SpinLockRelease(&fc_s->mutex);

		if (fc_restart_lsn == InvalidXLogRecPtr)
			continue;

		if (fc_result == InvalidXLogRecPtr ||
			fc_restart_lsn < fc_result)
			fc_result = fc_restart_lsn;
	}

	LWLockRelease(ReplicationSlotControlLock);

	return fc_result;
}

/*
 * ReplicationSlotsCountDBSlots -- 计算引用传递数据库 oid 的插槽数量。
 *
 * 如果有任何插槽引用该数据库，返回 true。*nslots 将被设置为数据库中的绝对插槽数量，*nactive 设置为当前活动的插槽数量。
 */
bool ReplicationSlotsCountDBSlots(Oid fc_dboid, int *fc_nslots, int *fc_nactive)
{
	int			fc_i;

	*fc_nslots = *fc_nactive = 0;

	if (max_replication_slots <= 0)
		return false;

	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s;

		fc_s = &ReplicationSlotCtl->replication_slots[fc_i];

		/* 在持有 ReplicationSlotCtlLock 时无法更改 */
		if (!fc_s->in_use)
			continue;

		/* 仅逻辑插槽是数据库特定的，跳过 */
		if (!SlotIsLogical(fc_s))
			continue;

		/* 不是我们的数据库，跳过 */
		if (fc_s->data.database != fc_dboid)
			continue;

		/* 持有自旋锁时计数插槽 */
		SpinLockAcquire(&fc_s->mutex);
		(*fc_nslots)++;
		if (fc_s->active_pid != 0)
			(*fc_nactive)++;
		SpinLockRelease(&fc_s->mutex);
	}
	LWLockRelease(ReplicationSlotControlLock);

	if (*fc_nslots > 0)
		return true;
	return false;
}

/*
 * ReplicationSlotsDropDBSlots -- 删除与传入数据库 oid 相关的所有特定于数据库的槽。调用者应对数据库的 pg_database oid 保持独占锁，以防止在数据库上创建新槽或从现有槽重放。
 *
 * 另一个会话如果同时在目标数据库上获取现有槽（最有可能是删除它），可能会导致此函数发生错误。如果发生这种情况，它可能已经删除了一些但不是所有的槽。
 *
 * 这个例程的效率可能不是很高 - 但我们不经常删除数据库，特别是具有许多槽的数据库。
 */
void ReplicationSlotsDropDBSlots(Oid fc_dboid)
{
	int			fc_i;

	if (max_replication_slots <= 0)
		return;

restart:
	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s;
		char	   *fc_slotname;
		int			fc_active_pid;

		fc_s = &ReplicationSlotCtl->replication_slots[fc_i];

		/* 在持有 ReplicationSlotCtlLock 时无法更改 */
		if (!fc_s->in_use)
			continue;

		/* 仅逻辑插槽是数据库特定的，跳过 */
		if (!SlotIsLogical(fc_s))
			continue;

		/* 不是我们的数据库，跳过 */
		if (fc_s->data.database != fc_dboid)
			continue;

		/* 获取槽，以便可以重用 ReplicationSlotDropAcquired */
		SpinLockAcquire(&fc_s->mutex);
		/* 在持有 ReplicationSlotControlLock 时无法更改 */
		fc_slotname = NameStr(fc_s->data.name);
		fc_active_pid = fc_s->active_pid;
		if (fc_active_pid == 0)
		{
			MyReplicationSlot = fc_s;
			fc_s->active_pid = MyProcPid;
		}
		SpinLockRelease(&fc_s->mutex);

		/*
		 * 尽管我们对数据库对象持有独占锁，但该数据库的逻辑槽仍然可以是活动的，例如，如果它正在被连接到另一个数据库的后端同时删除。
		 *
		 * 实际上这很不常见，所以我们将简单地退出。
		 */
		if (fc_active_pid)
			ereport(ERROR,
					(errcode(ERRCODE_OBJECT_IN_USE),
					 errmsg("replication slot \"%s\" is active for PID %d",
							fc_slotname, fc_active_pid)));

		/*
		 * 为了避免重复 ReplicationSlotDropAcquired() 并避免在文件系统操作期间持有 ReplicationSlotControlLock，
		 * 释放 ReplicationSlotControlLock 并使用 ReplicationSlotDropAcquired。
		 *
		 * 由于这意味着槽的集合可能会变化，因此在每次释放锁时从头开始重新扫描。
		 */
		LWLockRelease(ReplicationSlotControlLock);
		fc_ReplicationSlotDropAcquired();
		goto restart;
	}
	LWLockRelease(ReplicationSlotControlLock);
}


/*
 * 检查服务器的配置是否支持使用复制槽。
 */
void CheckSlotRequirements(void)
{
	/*
	 * 注意：添加新要求可能意味着 RestoreSlotFromDisk() 需要相同的检查。
	 */

	if (max_replication_slots == 0)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("replication slots can only be used if max_replication_slots > 0")));

	if (wal_level < WAL_LEVEL_REPLICA)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("replication slots can only be used if wal_level >= replica")));
}

/*
 * 检查用户是否有权限使用复制槽。
 */
void CheckSlotPermissions(void)
{
	if (!superuser() && !has_rolreplication(GetUserId()))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser or replication role to use replication slots")));
}

/*
 * 为当前活动槽保留 WAL。
 *
 * 以适合槽类型和并发安全的方式计算和设置 restart_lsn。
 */
void ReplicationSlotReserveWal(void)
{
	ReplicationSlot *fc_slot = MyReplicationSlot;

	Assert(fc_slot != NULL);
	Assert(fc_slot->data.restart_lsn == InvalidXLogRecPtr);

	/*
	 * 复制槽机制用于防止所需 WAL 的移除。由于此例程与检查点之间没有相互锁定，当使用 ReplicationSlotsComputeRequiredLSN() 的过时返回值时，WAL 段可能会同时被移除。在不太可能发生这种情况的情况下，我们将重试。
	 */
	while (true)
	{
		XLogSegNo	fc_segno;
		XLogRecPtr	fc_restart_lsn;

		/*
		 * 对于逻辑槽，记录一个备用快照并在确切位置开始逻辑解码。这允许槽更快地启动。
		 *
		 * 对于物理槽来说这并不是必需的（实际上也没有帮助），因为它们无论如何会在上一个登录的检查点处开始重放。相反，返回上一个重做 LSN 的位置。虽然这稍微增加了我们需要重试的机会，但这是基础备份必须开始重放的地方。
		 */
		if (!RecoveryInProgress() && SlotIsLogical(fc_slot))
		{
			XLogRecPtr	fc_flushptr;

			/* 从当前插入位置开始 */
			fc_restart_lsn = GetXLogInsertRecPtr();
			SpinLockAcquire(&fc_slot->mutex);
			fc_slot->data.restart_lsn = fc_restart_lsn;
			SpinLockRelease(&fc_slot->mutex);

			/* 确保我们有足够的信息来启动 */
			fc_flushptr = LogStandbySnapshot();

			/* 并确保它已 fsynced 到磁盘 */
			XLogFlush(fc_flushptr);
		}
		else
		{
			fc_restart_lsn = GetRedoRecPtr();
			SpinLockAcquire(&fc_slot->mutex);
			fc_slot->data.restart_lsn = fc_restart_lsn;
			SpinLockRelease(&fc_slot->mutex);
		}

		/* 防止尽快删除WAL */
		ReplicationSlotsComputeRequiredLSN();

		/*
		 * 如果所有所需的WAL仍然存在，那很好，否则重试。
		 * 插槽应该防止进一步删除WAL，除非在我们上面写了新的restart_lsn之后
		 * 有一个并发的ReplicationSlotsComputeRequiredLSN()，所以通常我们
		 * 不应该需要循环超过两次。
		 */
		XLByteToSeg(fc_slot->data.restart_lsn, fc_segno, wal_segment_size);
		if (XLogGetLastRemovedSegno() < fc_segno)
			break;
	}
}

/*
 * 用于失效过时复制槽的辅助函数 - 获取给定槽
 * 并在必要和可能的情况下将其标记为无效。
 *
 * 返回ReplicationSlotControlLock在此期间是否已被释放（在这种情况下
 * 我们在返回时未持有锁，否则我们持有锁）。
 *
 * 如果插槽被使为无效，则将*invalidated设置为true。（否则保持不变。）
 *
 * 这本质上是竞争条件，因为我们释放LWLock
 * 用于系统调用，因此如果我们返回true，调用者必须重新启动。
 */
static bool fc_InvalidatePossiblyObsoleteSlot(ReplicationSlot *fc_s, XLogRecPtr fc_oldestLSN,
							   bool *fc_invalidated)
{
	int			fc_last_signaled_pid = 0;
	bool		fc_released_lock = false;

	for (;;)
	{
		XLogRecPtr	fc_restart_lsn;
		NameData	fc_slotname;
		int			fc_active_pid = 0;

		Assert(LWLockHeldByMeInMode(ReplicationSlotControlLock, LW_SHARED));

		if (!fc_s->in_use)
		{
			if (fc_released_lock)
				LWLockRelease(ReplicationSlotControlLock);
			break;
		}

		/*
		 * 检查插槽是否需要失效。如果需要失效，且当前未被获取，
		 * 则获取它并标记为已失效。 我们在持有自旋锁的情况下做这件事，以
		 * 避免竞争条件 - 例如，restart_lsn可能会向前移动，或者插槽可能会被删除。
		 */
		SpinLockAcquire(&fc_s->mutex);

		fc_restart_lsn = fc_s->data.restart_lsn;

		/*
		 * 如果插槽已经无效或足够新，我们不需要做任何事情。
		 */
		if (XLogRecPtrIsInvalid(fc_restart_lsn) || fc_restart_lsn >= fc_oldestLSN)
		{
			SpinLockRelease(&fc_s->mutex);
			if (fc_released_lock)
				LWLockRelease(ReplicationSlotControlLock);
			break;
		}

		fc_slotname = fc_s->data.name;
		fc_active_pid = fc_s->active_pid;

		/*
		 * 如果插槽可以被获取，立刻获取并标记为失效。
		 * 否则，我们将在下面向拥有进程发送信号，并重试。
		 */
		if (fc_active_pid == 0)
		{
			MyReplicationSlot = fc_s;
			fc_s->active_pid = MyProcPid;
			fc_s->data.invalidated_at = fc_restart_lsn;
			fc_s->data.restart_lsn = InvalidXLogRecPtr;

			/* 让调用者知道 */
			*fc_invalidated = true;
		}

		SpinLockRelease(&fc_s->mutex);

		if (fc_active_pid != 0)
		{
			/*
			 * 在释放锁之前准备好在插槽的条件变量上休眠，以
			 * 关闭可能的竞争条件，如果插槽在下面的休眠之前被释放。
			 */
			ConditionVariablePrepareToSleep(&fc_s->active_cv);

			LWLockRelease(ReplicationSlotControlLock);
			fc_released_lock = true;

			/*
			 * 如果我们尚未向其发送信号，则向拥有插槽的进程发送信号以终止它。
			 * （避免重复发送信号是这个例程中存在循环的唯一原因；否则
			 * 我们可以依赖调用者的重启循环。）
			 *
			 * 存在一个竞争条件，即在其当前拥有进程被终止和此
			 * 进程拥有它之间，其他进程可能拥有该插槽。为了处理这个问题，
			 * 只有在拥有进程的PID与上次不同的情况下我们才会发送信号。
			 * （此逻辑假设相同的PID不会被非常快速地重用。）
			 */
			if (fc_last_signaled_pid != fc_active_pid)
			{
				ereport(LOG,
						(errmsg("terminating process %d to release replication slot \"%s\"",
								fc_active_pid, NameStr(fc_slotname))));

				(void) kill(fc_active_pid, SIGTERM);
				fc_last_signaled_pid = fc_active_pid;
			}

			/* 等待直到插槽被释放。 */
			ConditionVariableSleep(&fc_s->active_cv,
								   WAIT_EVENT_REPLICATION_SLOT_DROP);

			/*
			 * 重新获取锁并重新开始；我们期待下次失效该插槽
			 * （除非其他进程在此期间获取该插槽）。
			 */
			LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
			continue;
		}
		else
		{
			/*
			 * 我们现在持有该插槽并且已经使其失效；刷新它
			 * 以确保状态持久化。
			 *
			 * 我们不希望在文件系统操作期间持有ReplicationSlotControlLock，
			 * 所以现在释放它，但务必告诉调用者从头开始重新启动。
			 */
			LWLockRelease(ReplicationSlotControlLock);
			fc_released_lock = true;

			/* 确保失效的状态在服务器重启时持续存在 */
			ReplicationSlotMarkDirty();
			ReplicationSlotSave();
			ReplicationSlotRelease();

			ereport(LOG,
					(errmsg("invalidating slot \"%s\" because its restart_lsn %X/%X exceeds max_slot_wal_keep_size",
							NameStr(fc_slotname),
							LSN_FORMAT_ARGS(fc_restart_lsn))));

			/* 目前已完成此插槽 */
			break;
		}
	}

	Assert(fc_released_lock == !LWLockHeldByMe(ReplicationSlotControlLock));

	return fc_released_lock;
}

/*
 * 将指向比给定段更旧的LSN的任何槽标记为无效；
 * 这需要即将被删除的WAL。
 *
 * 当任何槽被失效时返回true。
 *
 * 注意 - 这作为检查点的一部分运行，因此如果可能的话避免抛出错误。
 */
bool InvalidateObsoleteReplicationSlots(XLogSegNo fc_oldestSegno)
{
	XLogRecPtr	fc_oldestLSN;
	bool		fc_invalidated = false;

	XLogSegNoOffsetToRecPtr(fc_oldestSegno, 0, wal_segment_size, fc_oldestLSN);

restart:
	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
	for (int fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s = &ReplicationSlotCtl->replication_slots[fc_i];

		if (!fc_s->in_use)
			continue;

		if (fc_InvalidatePossiblyObsoleteSlot(fc_s, fc_oldestLSN, &fc_invalidated))
		{
			/* 如果锁被释放，从头开始 */
			goto restart;
		}
	}
	LWLockRelease(ReplicationSlotControlLock);

	/*
	 * 如果有任何槽被失效，重新计算资源限制。
	 */
	if (fc_invalidated)
	{
		ReplicationSlotsComputeRequiredXmin(false);
		ReplicationSlotsComputeRequiredLSN();
	}

	return fc_invalidated;
}

/*
 * 将所有复制槽刷新到磁盘。
 *
 * 这实际上不必是检查点的一部分，但这是一个方便的位置。
 */
void CheckPointReplicationSlots(void)
{
	int			fc_i;

	elog(DEBUG1, "performing replication slot checkpoint");

	/*
	 * 在我们活动时防止创建/删除任何槽。因为我们
	 * 明确地不想阻止遍历复制槽或
	 * 获取无法控制的槽，我们无法获取控制锁 - 但这没关系，
	 * 因为持有ReplicationSlotAllocationLock是严格更强的，且
	 * 足以保证没有人可以更改我们的in_use位。
	 */
	LWLockAcquire(ReplicationSlotAllocationLock, LW_SHARED);

	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s = &ReplicationSlotCtl->replication_slots[fc_i];
		char		fc_path[MAXPGPATH];

		if (!fc_s->in_use)
			continue;

		/* 将槽保存到磁盘，锁定在SaveSlotToPath()中处理 */
		sprintf(fc_path, "pg_replslot/%s", NameStr(fc_s->data.name));
		fc_SaveSlotToPath(fc_s, fc_path, LOG);
	}
	LWLockRelease(ReplicationSlotAllocationLock);
}

/*
 * 在服务器启动时从磁盘加载所有复制槽到内存中。这
 * 必须在我们开始崩溃恢复之前运行。
 */
void
#ifdef FDD
StartupReplicationSlots(bool fc_drop_nonfailover_slots)
#else
StartupReplicationSlots(void)
#endif
{
	DIR		   *fc_replication_dir;
	struct dirent *fc_replication_de;

	elog(DEBUG1, "starting up replication slots");

	/* 通过遍历所有磁盘上的条目恢复所有槽 */
	fc_replication_dir = AllocateDir("pg_replslot");
	while ((fc_replication_de = ReadDir(fc_replication_dir, "pg_replslot")) != NULL)
	{
		struct stat fc_statbuf;
		char		fc_path[MAXPGPATH + 12];

		if (strcmp(fc_replication_de->d_name, ".") == 0 ||
			strcmp(fc_replication_de->d_name, "..") == 0)
			continue;

		snprintf(fc_path, sizeof(fc_path), "pg_replslot/%s", fc_replication_de->d_name);

		/* 这里只有创建目录，如果不是我们的则跳过 */
		if (lstat(fc_path, &fc_statbuf) == 0 && !S_ISDIR(fc_statbuf.st_mode))
			continue;

		/* 我们在设置或删除槽时崩溃，清理 */
		if (pg_str_endswith(fc_replication_de->d_name, ".tmp"))
		{
			if (!rmtree(fc_path, true))
			{
				ereport(WARNING,
						(errmsg("could not remove directory \"%s\"",
								fc_path)));
				continue;
			}
			fsync_fname("pg_replslot", true);
			continue;
		}

		/* 看起来一个槽处于正常状态，恢复 */
#ifdef FDD
		fc_RestoreSlotFromDisk(fc_replication_de->d_name, fc_drop_nonfailover_slots);
#else
		fc_RestoreSlotFromDisk(fc_replication_de->d_name);
#endif
	}
	FreeDir(fc_replication_dir);

	/* 目前没有槽存在，我们完成了。 */
	if (max_replication_slots <= 0)
		return;

	/* 现在我们已经恢复了所有数据，计算复制xmin */
	ReplicationSlotsComputeRequiredXmin(false);
	ReplicationSlotsComputeRequiredLSN();
}

/* ----
 * 操作复制槽的磁盘状态
 *
 * 注意：以下任何例程都不应关注槽是否是
 * 当前的，所有这些都在更高层处理。
 * ----
 */
static void fc_CreateSlotOnDisk(ReplicationSlot *fc_slot)
{
	char		fc_tmppath[MAXPGPATH];
	char		fc_path[MAXPGPATH];
	struct stat fc_st;

	/*
	 * 不需要拿出io_in_progress_lock，没有其他人可以看到这个
	 * 槽，所以没有其他人会写。我们重用了SaveSlotToPath，它
	 * 获取了锁，如果我们在这里获取锁，就会发生死锁。
	 */

	sprintf(fc_path, "pg_replslot/%s", NameStr(fc_slot->data.name));
	sprintf(fc_tmppath, "pg_replslot/%s.tmp", NameStr(fc_slot->data.name));

	/*
	 * 很可能之前尝试创建或删除一个名为
	 * 这个槽的槽留下了一个临时目录。如果确实是这种情况，试着移除它。
	 * 如果rmtree()失败，我们将在下面的MakePGDirectory()中抛出错误，
	 * 所以我们不麻烦检查成功。
	 */
	if (stat(fc_tmppath, &fc_st) == 0 && S_ISDIR(fc_st.st_mode))
		rmtree(fc_tmppath, true);

	/* 创建并同步临时槽目录。 */
	if (MakePGDirectory(fc_tmppath) < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not create directory \"%s\": %m",
						fc_tmppath)));
	fsync_fname(fc_tmppath, true);

	/* 写入实际状态文件。 */
	fc_slot->dirty = true;			/* 发出我们确实需要写入的信号 */
	fc_SaveSlotToPath(fc_slot, fc_tmppath, ERROR);

	/* 将目录重命名到正确的位置。 */
	if (rename(fc_tmppath, fc_path) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not rename file \"%s\" to \"%s\": %m",
						fc_tmppath, fc_path)));

	/*
	 * 如果我们现在失败 - 实际上不太可能 - 我们将不知道这个槽
	 * 在操作系统崩溃后是否会保留 - 所以，强制重启。重启将尝试
	 * 再次同步，直到成功。
	 */
	START_CRIT_SECTION();

	fsync_fname(fc_path, true);
	fsync_fname("pg_replslot", true);

	END_CRIT_SECTION();
}

/*
 * 在保存和创建复制槽之间的共享功能。
 */
 #ifdef FDD
 /* 
  * 对于故障转移槽，这是我们发出xlog的地方。
  */
 #endif
static void fc_SaveSlotToPath(ReplicationSlot *fc_slot, const char *fc_dir, int fc_elevel)
{
	char		fc_tmppath[MAXPGPATH];
	char		fc_path[MAXPGPATH];
	int			fc_fd;
	ReplicationSlotOnDisk fc_cp;
	bool		fc_was_dirty;

#ifdef FDD
	if (!RecoveryInProgress())
	{
#endif

	/* 首先检查是否有需要写入的内容 */
	SpinLockAcquire(&fc_slot->mutex);
	fc_was_dirty = fc_slot->dirty;
	fc_slot->just_dirtied = false;
	SpinLockRelease(&fc_slot->mutex);

	/* 如果没有内容可写，则不执行任何操作 */
	if (!fc_was_dirty)
		return;
#ifdef FDD
	}
#endif

	LWLockAcquire(&fc_slot->io_in_progress_lock, LW_EXCLUSIVE);

	/* 安静的valgrind :( */
	memset(&fc_cp, 0, sizeof(ReplicationSlotOnDisk));

	sprintf(fc_tmppath, "%s/state.tmp", fc_dir);
	sprintf(fc_path, "%s/state", fc_dir);

	fc_fd = OpenTransientFile(fc_tmppath, O_CREAT | O_EXCL | O_WRONLY | PG_BINARY);
	if (fc_fd < 0)
	{
		/*
		 * 如果不是ERROR，则在返回之前释放锁。在出现ERROR的情况下，
		 * 错误恢复路径会自动释放锁，但在这种情况下显式释放也无妨。
		 * 请注意，LWLockRelease() 可能会影响 errno。
		 */
		int			fc_save_errno = errno;

		LWLockRelease(&fc_slot->io_in_progress_lock);
		errno = fc_save_errno;
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not create file \"%s\": %m",
						fc_tmppath)));
		return;
	}

	fc_cp.magic = SLOT_MAGIC;
	INIT_CRC32C(fc_cp.checksum);
	fc_cp.version = SLOT_VERSION;
	fc_cp.length = ReplicationSlotOnDiskV2Size;

	SpinLockAcquire(&fc_slot->mutex);

	memcpy(&fc_cp.slotdata, &fc_slot->data, sizeof(ReplicationSlotPersistentData));

	SpinLockRelease(&fc_slot->mutex);
#ifdef FDD
	/*
	 * 如有需要，在WAL中记录此操作
	 */
	if (fc_slot->data.failover &&
		fc_slot->data.persistency == RS_PERSISTENT &&
		!RecoveryInProgress())
	{
		XLogBeginInsert();
		XLogRegisterData((char *) (&fc_cp.slotdata), sizeof(ReplicationSlotPersistentData));
		/*
		 * 注意，下游的插槽创建也算是一个“更新”。
		 *
		 * 插槽可以一开始是短暂的，然后更新为持久的。我们只需
		 * 记录更新，如果下游插槽尚不存在，则创建新插槽。
		 */
		(void) XLogInsert(RM_REPLSLOT_ID, XLOG_REPLSLOT_UPDATE);
	}
#endif

	COMP_CRC32C(fc_cp.checksum,
				(char *) (&fc_cp) + ReplicationSlotOnDiskNotChecksummedSize,
				ReplicationSlotOnDiskChecksummedSize);
	FIN_CRC32C(fc_cp.checksum);

	errno = 0;
	pgstat_report_wait_start(WAIT_EVENT_REPLICATION_SLOT_WRITE);
	if ((write(fc_fd, &fc_cp, sizeof(fc_cp))) != sizeof(fc_cp))
	{
		int			fc_save_errno = errno;

		pgstat_report_wait_end();
		CloseTransientFile(fc_fd);
		LWLockRelease(&fc_slot->io_in_progress_lock);

		/* 如果写入未设置 errno，假设问题是没有磁盘空间 */
		errno = fc_save_errno ? fc_save_errno : ENOSPC;
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not write to file \"%s\": %m",
						fc_tmppath)));
		return;
	}
	pgstat_report_wait_end();

	/* 同步临时文件 */
	pgstat_report_wait_start(WAIT_EVENT_REPLICATION_SLOT_SYNC);
	if (pg_fsync(fc_fd) != 0)
	{
		int			fc_save_errno = errno;

		pgstat_report_wait_end();
		CloseTransientFile(fc_fd);
		LWLockRelease(&fc_slot->io_in_progress_lock);
		errno = fc_save_errno;
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not fsync file \"%s\": %m",
						fc_tmppath)));
		return;
	}
	pgstat_report_wait_end();

	if (CloseTransientFile(fc_fd) != 0)
	{
		int			fc_save_errno = errno;

		LWLockRelease(&fc_slot->io_in_progress_lock);
		errno = fc_save_errno;
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m",
						fc_tmppath)));
		return;
	}

	/* 重命名为永久文件，同步文件和目录 */
	if (rename(fc_tmppath, fc_path) != 0)
	{
		int			fc_save_errno = errno;

		LWLockRelease(&fc_slot->io_in_progress_lock);
		errno = fc_save_errno;
		ereport(fc_elevel,
				(errcode_for_file_access(),
				 errmsg("could not rename file \"%s\" to \"%s\": %m",
						fc_tmppath, fc_path)));
		return;
	}

	/*
	 * 查看 CreateSlotOnDisk() 了解使用临界区的原因。
	 */
	START_CRIT_SECTION();

	fsync_fname(fc_path, false);
	fsync_fname(fc_dir, true);
	fsync_fname("pg_replslot", true);

	END_CRIT_SECTION();

	/*
	 * 成功写入，取消脏位，除非有人再次使其变脏。
	 */
	SpinLockAcquire(&fc_slot->mutex);
	if (!fc_slot->just_dirtied)
		fc_slot->dirty = false;
	SpinLockRelease(&fc_slot->mutex);

	LWLockRelease(&fc_slot->io_in_progress_lock);
}

/*
 * 从磁盘加载单个插槽到内存中。
 */
static void
#ifdef FDD
fc_RestoreSlotFromDisk(const char *fc_name, bool fc_drop_nonfailover_slots)
#else
fc_RestoreSlotFromDisk(const char *fc_name)
#endif
{
	ReplicationSlotOnDisk fc_cp;
	int			fc_i;
	char		fc_slotdir[MAXPGPATH + 12];
	char		fc_path[MAXPGPATH + 22];
	int			fc_fd;
	bool		fc_restored = false;
	int			fc_readBytes;
	pg_crc32c	fc_checksum;

	/* 此处无需锁定，尚未允许并发访问 */

	/* 如果存在，则删除临时文件 */
	sprintf(fc_slotdir, "pg_replslot/%s", fc_name);
	sprintf(fc_path, "%s/state.tmp", fc_slotdir);
	if (unlink(fc_path) < 0 && errno != ENOENT)
		ereport(PANIC,
				(errcode_for_file_access(),
				 errmsg("could not remove file \"%s\": %m", fc_path)));

	sprintf(fc_path, "%s/state", fc_slotdir);

	elog(DEBUG1, "restoring replication slot from \"%s\"", fc_path);

	/* 在某些操作系统上，fsync 文件需要 O_RDWR */
	fc_fd = OpenTransientFile(fc_path, O_RDWR | PG_BINARY);

	/*
	 * 我们不需要处理这个，因为我们只是在同步状态文件之后
	 * 重命名目录到位。
	 */
	if (fc_fd < 0)
		ereport(PANIC,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m", fc_path)));

	/*
	 * 在我们从状态文件读取之前同步状态文件。我们可能在
	 * 文件尚未同步时崩溃，基于此继续是不应该的。
	 */
	pgstat_report_wait_start(WAIT_EVENT_REPLICATION_SLOT_RESTORE_SYNC);
	if (pg_fsync(fc_fd) != 0)
		ereport(PANIC,
				(errcode_for_file_access(),
				 errmsg("could not fsync file \"%s\": %m",
						fc_path)));
	pgstat_report_wait_end();

	/* 也同步父目录 */
	START_CRIT_SECTION();
	fsync_fname(fc_slotdir, true);
	END_CRIT_SECTION();

	/* 读取保证与版本无关的状态文件部分 */
	pgstat_report_wait_start(WAIT_EVENT_REPLICATION_SLOT_READ);
	fc_readBytes = read(fc_fd, &fc_cp, ReplicationSlotOnDiskConstantSize);
	pgstat_report_wait_end();
	if (fc_readBytes != ReplicationSlotOnDiskConstantSize)
	{
		if (fc_readBytes < 0)
			ereport(PANIC,
					(errcode_for_file_access(),
					 errmsg("could not read file \"%s\": %m", fc_path)));
		else
			ereport(PANIC,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("could not read file \"%s\": read %d of %zu",
							fc_path, fc_readBytes,
							(Size) ReplicationSlotOnDiskConstantSize)));
	}

	/* 验证魔法数 */
	if (fc_cp.magic != SLOT_MAGIC)
		ereport(PANIC,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg("replication slot file \"%s\" has wrong magic number: %u instead of %u",
						fc_path, fc_cp.magic, SLOT_MAGIC)));

	/* 验证版本 */
	if (fc_cp.version != SLOT_VERSION)
		ereport(PANIC,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg("replication slot file \"%s\" has unsupported version %u",
						fc_path, fc_cp.version)));

	/* 长度边界检查 */
	if (fc_cp.length != ReplicationSlotOnDiskV2Size)
		ereport(PANIC,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg("replication slot file \"%s\" has corrupted length %u",
						fc_path, fc_cp.length)));

	/* 现在我们知道大小，读取整个文件 */
	pgstat_report_wait_start(WAIT_EVENT_REPLICATION_SLOT_READ);
	fc_readBytes = read(fc_fd,
					 (char *) &fc_cp + ReplicationSlotOnDiskConstantSize,
					 fc_cp.length);
	pgstat_report_wait_end();
	if (fc_readBytes != fc_cp.length)
	{
		if (fc_readBytes < 0)
			ereport(PANIC,
					(errcode_for_file_access(),
					 errmsg("could not read file \"%s\": %m", fc_path)));
		else
			ereport(PANIC,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("could not read file \"%s\": read %d of %zu",
							fc_path, fc_readBytes, (Size) fc_cp.length)));
	}

	if (CloseTransientFile(fc_fd) != 0)
		ereport(PANIC,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m", fc_path)));

	/* 现在验证CRC */
	INIT_CRC32C(fc_checksum);
	COMP_CRC32C(fc_checksum,
				(char *) &fc_cp + ReplicationSlotOnDiskNotChecksummedSize,
				ReplicationSlotOnDiskChecksummedSize);
	FIN_CRC32C(fc_checksum);

	if (!EQ_CRC32C(fc_checksum, fc_cp.checksum))
		ereport(PANIC,
				(errmsg("checksum mismatch for replication slot file \"%s\": is %u, should be %u",
						fc_path, fc_checksum, fc_cp.checksum)));

#ifdef FDD
	/*
	 * 如果我们在有活动短暂插槽的情况下崩溃，不要恢复而是
	 * 删除它。
	 *
	 * 同样，如果我们处于归档恢复中并将作为站备运行
	 * （当 drop_nonfailover_slots 被设置时），则非故障转移
	 * 插槽不能被依赖。逻辑插槽的目录
	 * xmin 可能低于实际，因为主节点上的原始插槽已经
	 * 超过了副本上的过时插槽的卡住点。此外，如果基本备份
	 * 复制方法不是原子的，插槽可能在写入时被复制。
	 * 故障转移插槽是安全的，因为它们是WAL-记录的并遵循
	 * 主节点的插槽位置。
	 */
	if (fc_cp.slotdata.persistency != RS_PERSISTENT || (fc_drop_nonfailover_slots && !fc_cp.slotdata.failover))
#else
	/*
	 * 如果我们在有活动短暂插槽的情况下崩溃，不要恢复而是删除
	 * 它。
	 */
	if (fc_cp.slotdata.persistency != RS_PERSISTENT)
#endif
	{
		if (!rmtree(fc_slotdir, true))
		{
			ereport(WARNING,
					(errmsg("could not remove directory \"%s\"",
							fc_slotdir)));
		}
		fsync_fname("pg_replslot", true);
#ifdef FDD
		if (fc_cp.slotdata.persistency == RS_PERSISTENT)
		{
			ereport(LOG,
					(errmsg("dropped non-failover slot %s during archive recovery",
							 NameStr(fc_cp.slotdata.name))));
		}
#endif
		return;
	}

	/*
	 * 验证特定插槽类型的要求是否满足。这很重要，因为如果这些要求不满足，我们不能保证保留插槽所需的所有资源。
	 *
	 * 注意：我们必须在上述对临时插槽的检查之后进行此操作，
	 * 因为否则一个不应该再存在的插槽可能会阻止重启。
	 *
	 * 注意：在这里改变要求还需要适应
	 * CheckSlotRequirements() 和 CheckLogicalDecodingRequirements()。
	 */
	if (fc_cp.slotdata.database != InvalidOid && wal_level < WAL_LEVEL_LOGICAL)
		ereport(FATAL,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("logical replication slot \"%s\" exists, but wal_level < logical",
						NameStr(fc_cp.slotdata.name)),
				 errhint("Change wal_level to be logical or higher.")));
	else if (wal_level < WAL_LEVEL_REPLICA)
		ereport(FATAL,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("physical replication slot \"%s\" exists, but wal_level < replica",
						NameStr(fc_cp.slotdata.name)),
				 errhint("Change wal_level to be replica or higher.")));

	/* 还没有任何可以处于活动状态，不要锁定任何东西 */
	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_slot;

		fc_slot = &ReplicationSlotCtl->replication_slots[fc_i];

		if (fc_slot->in_use)
			continue;

		/* 恢复整个持久数据集 */
		memcpy(&fc_slot->data, &fc_cp.slotdata,
			   sizeof(ReplicationSlotPersistentData));

		/* 初始化内存状态 */
		fc_slot->effective_xmin = fc_cp.slotdata.xmin;
		fc_slot->effective_catalog_xmin = fc_cp.slotdata.catalog_xmin;

		fc_slot->candidate_catalog_xmin = InvalidTransactionId;
		fc_slot->candidate_xmin_lsn = InvalidXLogRecPtr;
		fc_slot->candidate_restart_lsn = InvalidXLogRecPtr;
		fc_slot->candidate_restart_valid = InvalidXLogRecPtr;

		fc_slot->in_use = true;
		fc_slot->active_pid = 0;

		fc_restored = true;
		break;
	}

	if (!fc_restored)
		ereport(FATAL,
				(errmsg("too many replication slots active before shutdown"),
				 errhint("Increase max_replication_slots and try again.")));
}

#ifdef FDD
/*
 * 这通常只是将新的持久数据写入插槽状态，但如果我们将一个
 * 先前的临时插槽更改为持久插槽，更新记录可能会在下游创建一个新插槽。我们必须通过查找现有插槽来决定哪个。
 */
static void fc_ReplicationSlotRedoCreateOrUpdate(ReplicationSlotPersistentData * fc_xlrec)
{
	ReplicationSlot *fc_slot;
	bool	fc_found_available = false;
	bool	fc_found_duplicate = false;
	int		fc_use_slotid = 0;
	int		fc_i;

	/*
	 * 我们在重做中，但仍然可能有人创建一个本地
	 * 非故障转移插槽并与我们发生竞争，除非我们获得
	 * 分配锁。
	 */
	LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);

	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		fc_slot = &ReplicationSlotCtl->replication_slots[fc_i];

		/*
		 * 在插槽数组中找到第一个未使用的位置，但继续
		 * 扫描以防存在同名的现有插槽。
		 */
		if (!fc_slot->in_use && !fc_found_available)
		{
			fc_use_slotid = fc_i;
			fc_found_available = true;
		}

		/*
		 * 同名的现有插槽？它可能是我们要更新的故障转移插槽
		 * 或者是一个名称冲突的非故障转移插槽。
		 */
		if (strcmp(NameStr(fc_xlrec->name), NameStr(fc_slot->data.name)) == 0)
		{
			fc_use_slotid = fc_i;
			fc_found_available = true;
			fc_found_duplicate = true;
			break;
		}
	}

	if (fc_found_duplicate && !fc_slot->data.failover)
	{
		/*
		 * 已存在的本地非故障转移插槽与我们正在创建的
		 * 故障转移插槽同名。
		 *
		 * 替换客户端，删除它的插槽，并继续我们的工作。
		 *
		 * 首先，我们必须在尝试终止持有插槽的进程时
		 * 暂时释放分配锁，因为我们不想长时间持有 LWlock。
		 * 我们稍后会重新获取它。
		 */
		LWLockRelease(ReplicationSlotAllocationLock);

		/* 我们可能与其他客户端竞赛，因此重试循环 */
		do
		{
			int fc_active_pid = fc_slot->active_pid;
			int fc_max_sleep_millis = 120 * 1000;
			int fc_millis_per_sleep = 1000;

			if (fc_active_pid != 0)
			{
				ereport(INFO,
						(errmsg("terminating active connection by pid %u to local slot %s because of conflict with recovery",
							fc_active_pid, NameStr(fc_slot->data.name))));

				if (kill(fc_active_pid, SIGTERM))
					elog(DEBUG1, "failed to signal pid %u to terminate on slot conflict: %m",
							fc_active_pid);

				/*
				 * 等待使用该插槽的进程结束。这里只是使用
				 * 触发器进行轮询；当进程释放
				 * 插槽并结束时不会设置我们的触发器。
				 *
				 * 我们在没有持有任何锁的情况下检查 active_pid，
				 * 但在我们尝试删除插槽时会重新检查。
				 */
				while (fc_slot->in_use && fc_slot->active_pid == fc_active_pid
						&& fc_max_sleep_millis > 0)
				{
					int fc_rc;

					fc_rc = WaitLatch(MyLatch,
							WL_TIMEOUT | WL_LATCH_SET | WL_POSTMASTER_DEATH,
							fc_millis_per_sleep,WAIT_EVENT_PG_SLEEP);

					if (fc_rc & WL_POSTMASTER_DEATH)
						elog(FATAL, "exiting after postmaster termination");

					/*
					 * 如果某些东西设置了我们的触发器可能会更短，但
					 * 我们不太在乎。
					 */
					fc_max_sleep_millis -= fc_millis_per_sleep;
				}

				if (fc_max_sleep_millis <= 0)
					elog(WARNING, "process %u is taking too long to terminate after SIGTERM",
							fc_slot->active_pid);
			}

			if (fc_slot->active_pid == 0)
			{
				/* 尝试获取并删除插槽 */
				SpinLockAcquire(&fc_slot->mutex);

				if (fc_slot->active_pid != 0)
				{
					/* 输掉了竞赛，重新开始 */
				}
				else
				{
					/* 为自己声称该插槽 */
					fc_slot->active_pid = MyProcPid;
					MyReplicationSlot = fc_slot;
				}
				SpinLockRelease(&fc_slot->mutex);
			}

			if (fc_slot->active_pid == MyProcPid)
			{
				NameData fc_slotname;
				strncpy(NameStr(fc_slotname), NameStr(fc_slot->data.name), NAMEDATALEN);
				(NameStr(fc_slotname))[NAMEDATALEN-1] = '\0';

				/*
				 * 重新获取分配锁，然后删除插槽，
				 * 以便在我们完成重做之前没有其他人能够抢占名称。
				 */
				LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
				fc_ReplicationSlotDropAcquired();
				/* 我们覆盖了重复项，将其视为新项 */
				fc_found_duplicate = false;

				ereport(WARNING,
						(errmsg("dropped local replication slot %s because of conflict with recovery",
								NameStr(fc_slotname)),
						 errdetail("A failover slot with the same name was created on the master server")));
			}
		}
		while (fc_slot->in_use);
	}

	Assert(LWLockHeldByMe(ReplicationSlotAllocationLock));

	/*
	 * 这要么是一个空插槽控制位置以创建新插槽，要么是
	 * 需要更新的故障转移插槽的现有条目。
	 */
	if (fc_found_available)
	{
		LWLockAcquire(ReplicationSlotControlLock, LW_EXCLUSIVE);

		fc_slot = &ReplicationSlotCtl->replication_slots[fc_use_slotid];

		/* 恢复整个持久数据集 */
		memcpy(&fc_slot->data, fc_xlrec,
			   sizeof(ReplicationSlotPersistentData));

		Assert(strcmp(NameStr(fc_xlrec->name), NameStr(fc_slot->data.name)) == 0);
		Assert(fc_slot->data.persistency == RS_PERSISTENT);
		/*
		 * 它是通过 WAL 进入的，所以它必须是故障转移插槽。标记它，以便
		 * 在写出时创建插槽标记文件。
		 */
		fc_slot->data.failover = true;

		/* 更新非持久的内存状态 */
		fc_slot->effective_xmin = fc_xlrec->xmin;
		fc_slot->effective_catalog_xmin = fc_xlrec->catalog_xmin;

		if (fc_found_duplicate && fc_slot->in_use)
		{
			char		fc_path[MAXPGPATH];

			/* 将现有插槽写入磁盘 */
			Assert(fc_slot->in_use);
			Assert(fc_slot->active_pid == 0); /* 不能从故障转移槽恢复 */

			sprintf(fc_path, "pg_replslot/%s", NameStr(fc_slot->data.name));
			fc_slot->dirty = true;
			fc_SaveSlotToPath(fc_slot, fc_path, ERROR);
		}
		else
		{
			Assert(!fc_slot->in_use);
			/* 仅在创建时设置的内存状态，而不是更新时 */
			fc_slot->active_pid = 0;
			fc_slot->in_use = true;
			fc_slot->candidate_catalog_xmin = InvalidTransactionId;
			fc_slot->candidate_xmin_lsn = InvalidXLogRecPtr;
			fc_slot->candidate_restart_lsn = InvalidXLogRecPtr;
			fc_slot->candidate_restart_valid = InvalidXLogRecPtr;

			fc_CreateSlotOnDisk(fc_slot);
		}

		LWLockRelease(ReplicationSlotControlLock);

		ReplicationSlotsComputeRequiredXmin(false);
		ReplicationSlotsComputeRequiredLSN();
	}

	LWLockRelease(ReplicationSlotAllocationLock);

	if (!fc_found_available)
	{
		/*
		 * 因为备用应该具有与主服务器相同或更大的 max_replication_slots
		 * 所以不应该发生这种情况，但以防万一...
		 */
		ereport(ERROR,
				(errmsg("max_replication_slots exceeded, cannot replay failover slot creation"),
				 errhint("Increase max_replication_slots")));
	}
}

/*
 * 在故障转移槽上重做一个槽的删除。这可能是在主服务器崩溃恢复期间的重做
 * 或者可能是在备用服务器上的回放。
 */
static void fc_ReplicationSlotRedoDrop(const char * fc_slotname)
{
	/*
	 * 获取要删除的故障转移槽。
	 *
	 * 我们不能在这里调用 ReplicationSlotAcquire，因为我们想在回放期间获取
	 * 一个复制槽，这通常是不允许的。
	 * 此外，由于我们可能在删除过程中崩溃，我们不能
	 * 假设我们会找到槽，因此槽缺失并不是错误。
	 */
	int			fc_i;

	Assert(MyReplicationSlot == NULL);

	ReplicationSlotValidateName(fc_slotname, ERROR);

	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
	for (fc_i = 0; fc_i < max_replication_slots; fc_i++)
	{
		ReplicationSlot *fc_s = &ReplicationSlotCtl->replication_slots[fc_i];

		if (fc_s->in_use && strcmp(fc_slotname, NameStr(fc_s->data.name)) == 0)
		{
			if (!fc_s->data.persistency == RS_PERSISTENT)
			{
				/* 不应该发生 */
				elog(WARNING, "found conflicting non-persistent slot during failover slot drop");
				break;
			}

			/* 故障转移槽在恢复期间不能处于激活状态 */
			Assert(fc_s->active_pid == 0);

			/* 声明槽 */
			fc_s->active_pid = MyProcPid;
			MyReplicationSlot = fc_s;

			break;
		}
	}
	LWLockRelease(ReplicationSlotControlLock);

	if (MyReplicationSlot != NULL)
	{
		fc_ReplicationSlotDropAcquired();
	}
	else
	{
		elog(WARNING, "failover slot %s not found during redo of drop",
				fc_slotname);
	}
}

void replslot_redo(XLogReaderState *fc_record)
{
	uint8		fc_info = XLogRecGetInfo(fc_record) & ~XLR_INFO_MASK;

	if(!StandbyMode)
	{
		//流复制主机不能回放故障转移槽相关的wal
		return;
	}
	switch (fc_info)
	{
		/*
		 * 更新现有故障转移槽的值，或者当一个槽
		 * 首次记录为持久时，在下游创建它。
		 */
		case XLOG_REPLSLOT_UPDATE:
			fc_ReplicationSlotRedoCreateOrUpdate((ReplicationSlotPersistentData *) XLogRecGetData(fc_record));
			break;

		/*
		 * 删除一个现有的故障转移槽。
		 */
		case XLOG_REPLSLOT_DROP:
			{
				xl_replslot_drop *fc_xlrec =
				(xl_replslot_drop *) XLogRecGetData(fc_record);

				fc_ReplicationSlotRedoDrop(NameStr(fc_xlrec->name));

				break;
			}

		default:
			elog(PANIC, "replslot_redo: unknown op code %u", fc_info);
	}
}

#endif
